package hos.retrofit;

import static hos.retrofit.Utils.throwIfFatal;

import java.io.IOException;
import java.util.Objects;

/**
 * <p>Title: RetrofitCallReal </p>
 * <p>Description:  </p>
 * <p>Company: www.mapuni.com </p>
 *
 * @author : 蔡俊峰
 * @version : 1.0
 * @date : 2024-04-13 21:41
 */
public class DefaultRetrofitCall<T> implements RetrofitCall<T> {
    private final RetrofitRequest request;
    private final Object instance;
    private final RetrofitConverter<Object, T> converter;

    private final RetrofitCall.Factory factory;


    private volatile boolean canceled;

    private RetrofitCall<Object> rawCall;

    private Throwable creationFailure;

    private boolean executed;

    public DefaultRetrofitCall(RetrofitRequest request, Object instance,
                               RetrofitConverter<Object, T> converter, Factory factory) {
        this.request = request;
        this.instance = instance;
        this.converter = converter;
        this.factory = factory;
    }

    private RetrofitCall<Object> createRawCall() throws IOException {
        RetrofitCall<Object> call = factory.newCall(request);
        if (call == null) {
            throw new NullPointerException("Call.Factory returned null.");
        }
        return call;
    }

    private RetrofitCall<?> getRawCall() throws IOException {
        RetrofitCall<?> call = rawCall;
        if (call != null) return call;

        // Re-throw previous failures if this isn't the first attempt.
        if (creationFailure != null) {
            if (creationFailure instanceof IOException) {
                throw (IOException) creationFailure;
            } else if (creationFailure instanceof RuntimeException) {
                throw (RuntimeException) creationFailure;
            } else {
                throw (Error) creationFailure;
            }
        }

        // Create and remember either the success or the failure.
        try {
            return rawCall = createRawCall();
        } catch (RuntimeException | Error | IOException e) {
            throwIfFatal(e); // Do not assign a fatal error to creationFailure.
            creationFailure = e;
            throw e;
        }
    }

    @Override
    public T execute() throws IOException {
        RetrofitCall<?> call;
        synchronized (this) {
            if (executed) throw new IllegalStateException("Already executed.");
            executed = true;

            call = getRawCall();
        }
        if (canceled) {
            call.cancel();
        }
        try {
            RetrofitInterceptor.RequestInterceptor requestInterceptor = request.getRequestInterceptor();
            if (requestInterceptor != null) {
                requestInterceptor.intercept(request);
            }
            T t = parseResult(call.execute());
            RetrofitInterceptor.ResponseInterceptor responseInterceptor = request.getResponseInterceptor();
            if (responseInterceptor != null) {
                responseInterceptor.intercept(t);
            }
            return t;
        } catch (IOException e) {
            RetrofitInterceptor.ExceptionInterceptor exceptionInterceptor = request.getExceptionInterceptor();
            if (exceptionInterceptor != null) {
                exceptionInterceptor.intercept(e);
            }
            throw e;
        }
    }

    @SuppressWarnings("unchecked")
    private T parseResult(Object response) throws IOException {
        if (converter == null) {
            return (T) response;
        }
        return converter.convert(response);
    }

    @Override
    public void enqueue(final RetrofitCallback<T> callback) {
        Objects.requireNonNull(callback, "callback == null");

        RetrofitCall<Object> call;
        Throwable failure;

        synchronized (this) {
            if (executed) throw new IllegalStateException("Already executed.");
            executed = true;

            call = rawCall;
            failure = creationFailure;
            if (call == null && failure == null) {
                try {
                    call = rawCall = createRawCall();
                } catch (Throwable t) {
                    throwIfFatal(t);
                    failure = creationFailure = t;
                }
            }
        }

        if (failure != null) {
            callback.onFailure(this, failure);
            return;
        }

        if (canceled) {
            call.cancel();
        }
        RetrofitInterceptor.RequestInterceptor requestInterceptor = request.getRequestInterceptor();
        if (requestInterceptor != null) {
            requestInterceptor.intercept(request);
        }
        call.enqueue(new RetrofitCallback<Object>() {
            @Override
            public void onResponse(RetrofitCall<Object> call, Object rawResponse) {
                T response;
                try {
                    response = parseResult(rawResponse);
                } catch (Throwable e) {
                    throwIfFatal(e);
                    callFailure(e);
                    return;
                }

                try {
                    RetrofitInterceptor.ResponseInterceptor responseInterceptor = request.getResponseInterceptor();
                    if (responseInterceptor != null) {
                        responseInterceptor.intercept(response);
                    }
                    callback.onResponse(DefaultRetrofitCall.this, response);
                } catch (Throwable t) {
                    throwIfFatal(t);
                    t.printStackTrace(); // TODO this is not great
                }
            }

            @Override
            public void onFailure(RetrofitCall<Object> call, Throwable t) {
                callFailure(t);
            }

            private void callFailure(Throwable e) {
                try {
                    RetrofitInterceptor.ExceptionInterceptor exceptionInterceptor = request.getExceptionInterceptor();
                    if (exceptionInterceptor != null) {
                        exceptionInterceptor.intercept(e);
                    }
                    callback.onFailure(DefaultRetrofitCall.this, e);
                } catch (Throwable t) {
                    throwIfFatal(t);
                    t.printStackTrace(); // TODO this is not great
                }
            }
        });
    }

    @Override
    public synchronized boolean isExecuted() {
        return executed;
    }

    @Override
    public void cancel() {
        canceled = true;

        RetrofitCall<?> call;
        synchronized (this) {
            call = rawCall;
        }
        if (call != null) {
            call.cancel();
        }
    }

    @Override
    public boolean isCanceled() {
        if (canceled) {
            return true;
        }
        synchronized (this) {
            return rawCall != null && rawCall.isCanceled();
        }
    }

    @Override
    public DefaultRetrofitCall<T> clone() {
        return new DefaultRetrofitCall<>(request, instance, converter, factory);
    }
}
